package com.kly.sc.api.application.interceptors;


import com.alibaba.nacos.api.config.annotation.NacosValue;
import com.fqgj.base.services.redis.RedisClient;
import com.fqgj.common.utils.JSONUtils;
import com.fqgj.common.utils.StringUtils;
import com.kly.chatter.api.application.service.BaseService;
import com.kly.chatter.api.application.service.RedisService;
import com.kly.sc.api.application.annos.SignIgnore;
import com.kly.sc.api.application.annos.VisitorAccessible;
import com.kly.sc.api.application.constant.ApiRedisKeys;
import com.kly.sc.api.application.helper.RequestLocalInfo;
import com.kly.sc.api.application.utils.AppSignUtil;
import com.kly.sc.api.application.utils.JwtUtils;
import com.kly.sc.api.application.vo.UserAccessInfo;
import com.kly.sc.api.infras.app.RequestBasicInfo;
import com.kly.sc.api.infras.config.NacosConfigUtil;
import com.kly.sc.api.infras.enums.RestCodeEnum;
import com.kly.sc.api.infras.exception.ApplicationException;
import com.kly.user.chatterService.ChatterUserService;
import com.kly.user.constant.RedisKey;
import com.kly.user.dto.chatter.response.UserChatterModeDto;
import com.kly.user.enums.OnlineStatus;
import com.kly.user.enums.RememberTime;
import com.kly.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.concurrent.CompletableFuture;

/**
 * Created with IntelliJ IDEA.
 * User: yujianfu (mr.vencnet@gmail.com)
 * Date: 2019-11-25
 * Time: 13:07
 */
@Slf4j
@Component
public class AccessInterceptor implements HandlerInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(AccessInterceptor.class);

    public static final Set<String> ALLOWED_PATHS = new HashSet<>(Arrays.asList("/ok", "/error", "/swagger-resources/configuration/ui"));

    @Resource
    private NacosConfigUtil nacosConfigUtil;

    @Resource
    private RedisClient redisClient;

    @Resource
    UserService userService;

    @NacosValue(value = "${chatter.app.version:9}", autoRefreshed = true)
    private Integer chatterAppVersion;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {

        String requestURI = request.getRequestURI();
        for (String s : ALLOWED_PATHS) {
            if (requestURI.equals(s)) {
                LOGGER.info("拦截器放行,url:{}", requestURI);
                return true;
            }
        }

        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type,Accept,X-Requested-With,remember-me,bid,basicParams");
        RequestLocalInfo.clearThreadLocal();

        HandlerMethod handlerMethod = (HandlerMethod) o;
        SignIgnore signIgnoreAnnotation = handlerMethod.getMethodAnnotation(SignIgnore.class);
        if (signIgnoreAnnotation != null) {
            String basicParams = request.getHeader("basicParams");
            RequestBasicInfo basicVO = JSONUtils.json2pojo(basicParams, RequestBasicInfo.class);
            if (Integer.parseInt(basicVO.getAppVersion()) < chatterAppVersion) {
                throw new ApplicationException(RestCodeEnum.APP_VERSION_EXPIRED);
            }
            RequestLocalInfo.putCurrentRequestBasicVO(basicVO);
            return true;
        }

        verifySignInfo(request);
        RequestBasicInfo basicVO = RequestLocalInfo.getRequestBasicInfo();
        if (Integer.parseInt(basicVO.getAppVersion()) < chatterAppVersion) {
            throw new ApplicationException(RestCodeEnum.APP_VERSION_EXPIRED);
        }
        verifyToken(basicVO.getToken(), handlerMethod, request);


        return true;
    }




    private void verifyToken(String accessToken, HandlerMethod handlerMethod, HttpServletRequest request) {
        //获取不要校验token的注解
        VisitorAccessible annotation = handlerMethod.getMethodAnnotation(VisitorAccessible.class);
        if (annotation != null && StringUtils.isEmpty(accessToken)) {
            return;
        }

        if (StringUtils.isEmpty(accessToken)) {
            throw new ApplicationException(RestCodeEnum.NEED_TOKEN_ERROR);
        }

        if (!hasPermission(accessToken, handlerMethod) && annotation == null) {
            LOGGER.error("User authentication failed");
            throw new ApplicationException(RestCodeEnum.TOKEN_NOT_AVAILABLE);
        }
    }

    private void verifySignInfo(HttpServletRequest request) throws Exception {
        String basicParams = request.getHeader("basicParams");
        if (StringUtils.isEmpty(basicParams)) {
            throw new ApplicationException(RestCodeEnum.SYSTEM_PARAMETER_ERROR);
        }
        RequestBasicInfo basicVO = JSONUtils.json2pojo(basicParams, RequestBasicInfo.class);
        boolean isH5 = RequestBasicInfo.wechatUser(basicVO.getChannel());
        if (!isH5 && basicVO.getVersionCode() < 1 && !request.getRequestURI().contains("/home")) {
            throw new ApplicationException(RestCodeEnum.APP_VERSION_EXPIRED);
        }
        boolean needVerifyAppSign = !isH5 && !request.getRequestURI().contains("appcode/get") && !request.getRequestURI().contains("assistance/submit");

        if (needVerifyAppSign && !AppSignUtil.verifyAppSign(basicVO, "", nacosConfigUtil.isServerTest())) {
            throw new ApplicationException(RestCodeEnum.PARAMETER_SIGN_ERROR);
        }

        RequestLocalInfo.putCurrentEnvIsTest(nacosConfigUtil.isServerTest());
        RequestLocalInfo.putCurrentRequestBasicVO(basicVO);
    }


    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, Exception e) throws Exception {

        try {
//            String url = httpServletRequest.getRequestURI();
//            Long startTime = Long.parseLong(MDC.get("startTime"));
//            Long costTime = System.currentTimeMillis() - startTime;

            //todo 增加访问历史日志记录
        } catch (Exception ex) {
            LOGGER.error("afterCompletion error:" + ex);
        }
    }

    public AccessInterceptor() {

    }

    private Boolean hasPermission(String accessToken, HandlerMethod handlerMethod) {

        if (!JwtUtils.validateToken(accessToken)) {
            throw new ApplicationException(RestCodeEnum.TOKEN_NOT_AVAILABLE);
        }

        String userCode = JwtUtils.getAuthentication(accessToken).getPrincipal().toString();

        String redisToken = redisClient.get("key_{}_{}", userCode, RedisKey.TOKEN_CODE);
        if (!accessToken.equals(redisToken)) {
            throw new ApplicationException(RestCodeEnum.TOKEN_NOT_AVAILABLE);
        }

        // chatter上线
        chatterOnline(userCode);


//        //获取用户信息
//        UserBaseDto userByUserCode;
//        String baseInfo = redisService.getString(ApiRedisKeys.USER_BASE_INFO.getValue(), userCode);
//        if (StringUtils.isEmpty(baseInfo)) {
//            userByUserCode = userService.getUserBaseInfo(userCode).getData();
//            if (userByUserCode == null) {
//                throw new ApplicationException(RestCodeEnum.USER_NOT_EXIST_ERROR);
//            }
//            redisClient.set(ApiRedisKeys.USER_BASE_INFO.getValue(),
//                    JSONUtil.toJsonStr(userByUserCode), RememberTime.ONE_DAY.getTime(), userCode);
//        } else {
//            userByUserCode = JSONUtil.toBean(baseInfo, UserBaseDto.class);
//        }


        //查询属于chat的mode
//        Map<String, UserChatterModeDto> modeMap = baseService.getModeMap(userCode);
//        List<String> list = new ArrayList<>(modeMap.keySet());
//        list.add(userCode);
//        //如果用户超过两分钟没有操作，则重新设置在线状态
//        if (redisClient.get(ApiRedisKeys.USER_ONLINE_STATUS.getValue(), userCode) == null) {
//
//            CompletableFuture.runAsync(() -> {
//                // 这里是您要异步执行的方法或代码块
//                try {
//                    LOGGER.info("saveOrUpdateChatOnlineRecord:{}",userCode);
//                    //修改CM在线状态
//                    userService.offlineByUserCodeList(new HashSet<>(list), OnlineStatus.ONLINE_STATUS);
//                    //添加chat离线队列，当到达过期时间后chat离线
//                    redisService.addDelayTask(ApiRedisKeys.CHAT_USER_ONLINE_QUEUE.getValue(),
//                            userCode, RememberTime.TNE_MINUTES.getTime(), "userOnlineQueue");
//                    //设置chat在线时间
//                    userService.saveOrUpdateChatOnlineRecord(userCode,1);
//                } catch (Exception e) {
//                    e.printStackTrace();
//                }
//            });
//
//
//            redisClient.set(ApiRedisKeys.USER_ONLINE_STATUS.getValue(), RememberTime.TWO_MINUTES.getCode(), RememberTime.TWO_MINUTES.getTime(), userCode);
//        }
//        //添加定时修改队列
//        redisService.addDelayTask(ApiRedisKeys.USER_ONLINE_QUEUE.getValue(), list, RememberTime.TNE_MINUTES.getTime(), "userOnlineQueue");
//
//        //添加chat未操作超时通知
//        redisService.addDelayTask(ApiRedisKeys.USER_OPERATION_INTERVAL.getValue(), userCode, RememberTime.TWO_MINUTES.getTime(), "userOperationInterval");


//        InitUser annotation = handlerMethod.getMethodAnnotation(InitUser.class);
//        if ((!userByUserCode.getIsUserValid())&&annotation==null) {
//            throw new ApplicationException(RestCodeEnum.USER_BANNED);
//        }

        RequestLocalInfo.putCurrentUser(new UserAccessInfo().setUserCode(userCode));
        return true;
    }

    private void chatterOnline(String userCode) {
        try {
            String onlineKey = "online_status_{}";
            Object onlineStatus = redisClient.get(onlineKey, userCode);
            if (Objects.isNull(onlineStatus)) {
                log.info("==用户上线==， userCode:{}", userCode);
                userService.updateOnline(userCode);
                redisClient.set(onlineKey, OnlineStatus.ONLINE_STATUS.getValue(), 120, userCode);
            }
        } catch (Exception e) {
            log.error("chatterOnlineError, userCode:{}", userCode, e);
        }
    }

}


